
using System;
using System.Threading;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Collections;
using GsmLink.Comm;
using GsmLink.Persistence;

namespace GsmLink
{

	public class GsmDevice
	{
		#region Fields
		
		private string name = "";
		private string manufacturer = "";
		private string model = "";
		private string serialNumber = "";
		private int batteryLevel = -1;
		private bool isUsingBattery = false;
		private int signalQuality = -1;

		private ICommDriver commDriver = null;	
		private IPersistenceProvider persistenceProvider = null;		
		private bool isStarted = false;
		private bool isConnected = false;		
		private bool asyncTasksStarted = false;
		private bool supportsPduMode = false;		
		private int sendMessageRetryCount = 5;
		private MessageStorage messageStorage = MessageStorage.Device;
		private SyncOptions syncOptions = SyncOptions.All;
		
		private Thread notifyThread = null;
		private Thread sendReceiveThread = null;
        private int messageToSend = 0;
		private ManualResetEvent notifySignal = null;
		private ManualResetEvent sendReceiveSignal = null;
		private Queue notifyQueue = new Queue();		
		private SmsMessageCollection inbox = new SmsMessageCollection();
		private SmsMessageCollection outbox = new SmsMessageCollection();
		private SmsMessageCollection sent = new SmsMessageCollection();		

		#endregion Fields
		
		#region Constructor, destructor

		/// <summary>
		/// Initializes a new instance of the <see cref="GsmDevice"/> class.
		/// </summary>
		public GsmDevice()
		{			
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GsmDevice"/> class with 
		/// the specified <b>Name</b>.
		/// </summary>
		public GsmDevice(string name)
		{			
			// Let the property setter validate the value.
			Name = name;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GsmDevice"/> class with 
		/// the specified <b>Name</b> and <see cref="ICommDriver"/> object.
		/// </summary>
		public GsmDevice(string name, ICommDriver commDriver)
		{
			// Let the property setters validate the values.
			Name = name;
			CommDriver = commDriver;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GsmDevice"/> class with 
		/// the specified <b>Name</b>, <see cref="ICommDriver"/> object, and
		/// <see cref="IPersistenceProvider"/> object.
		/// </summary>
		public GsmDevice(string name, ICommDriver commDriver, IPersistenceProvider persistenceProvider)
		{
			// Let the property setters validate the values.
			Name = name;
			CommDriver = commDriver;
			PersistenceProvider = persistenceProvider;
		}

		/// <summary>
		/// Allows the <see cref="GsmDevice"/> instance to perform cleanup 
		/// operations before the instance is reclaimed by garbage 
		/// collection.
		/// <para>
		/// In C# and C++, finalizers are expressed using destructor syntax.
		/// </para>
		/// </summary> 		
		~GsmDevice()
		{
			if (this.isStarted)
				Stop();
		}

		#endregion Constructor, destructor

		#region Properties

        public int NumberOfMessagesToSend 
        {
            get { return this.messageToSend; } 
            set { this.messageToSend = value; }
        }

		/// <summary>
		/// Gets or sets the user-assigned name of the <see cref="GsmDevice"/> instance.
		/// </summary>
		/// <value>
		/// The user-assigned name of the <see cref="GsmDevice"/> instance.
		/// </value>
		/// <remarks>
		/// The <b>Name</b> property is typically used to identify a specific 
		/// <see cref="GsmDevice"/> instance in a collection of 
		/// <see cref="GsmDevice"/> instances.
		/// <para>
		/// The default value of the <b>Name</b> property is "" (empty string).
		/// The <b>Name</b> property cannot be assigned a <b>null</b> value.
		/// </para>
		/// </remarks>
		/// <exception cref="ApplicationException">
		/// The value assigned to the <b>Name</b> property is null.
		/// </exception>  
		public string Name
		{
			get { return this.name; }
			set 
			{
				if (value != null)
				{
					this.name = value;					
				}
				else
				{
					string errorMsg = "The Name property cannot be null.";
					throw new ApplicationException(errorMsg);
				}
			}
		}

		/// <summary>
		/// Gets the name of the GSM device's manufacturer.
		/// </summary>
		/// <value>
		/// The name of the GSM device's manufacturer.
		/// </value>		
		public string Manufacturer
		{
			get { return this.manufacturer; }
		}

		/// <summary>
		/// Gets the model of the GSM device.
		/// </summary>
		/// <value>
		/// The model of the GSM device.
		/// </value>		
		public string Model
		{
			get { return this.model; }
		}

		/// <summary>
		/// Gets the serial number of the GSM device.
		/// </summary>
		/// <value>
		/// The serial number of the GSM device.
		/// </value>		
		public string SerialNumber
		{
			get { return this.serialNumber; }
		}

		/// <summary>
		/// Returns whether the physical GSM device is powered by a battery.
		/// </summary>
		/// <value>
		/// <b>true</b> if the GSM device is powered by a battery;
		/// <b>false</b> otherwise.
		/// </value>
		public bool IsUsingBattery
		{
			get { return this.isUsingBattery; }
		}
		
		/// <summary>
		/// Gets the charge level of the physical GSM device's battery, if present.
		/// </summary>
		/// <value>
		/// The charge level - represented by the values <b>1...100</b> - of the 
		/// physical GSM device's battery. If the GSM device is not powered by 
		/// a battery, the value is always 100.
		/// </value>		
		public int BatteryLevel
		{
			get { return this.batteryLevel; }
		}

		/// <summary>
		/// Gets the quality of the signal received by the physical GSM device.
		/// </summary>
		/// <value>
		/// The quality - represented by the values <b>0...100</b> - of the 
		/// signal received by the physical GSM device. A zero value means 
		/// that the signal quality is unknown, or not detected.
		/// </value>		
		public int SignalQuality
		{
			get { return this.signalQuality; }
		}		

		/// <summary>
		/// Gets or sets the <b>communication driver</b> used by the 
		/// <see cref="GsmDevice"/> instance.
		/// </summary>
		/// <value>
		/// The <see cref="GsmLink.Comm.ICommDriver"/> object that is 
		/// currently used by the <see cref="GsmDevice"/> instance to
		/// communicate with the physical GSM device.
		/// </value>
		/// <remarks>
		/// The <b>CommDriver</b> property must be assigned a valid
		/// <see cref="GsmLink.Comm.ICommDriver"/> object before invoking the 
		/// <see cref="GsmDevice.Start"/> method on the <see cref="GsmDevice"/>
		/// instance. An <see cref="GsmLink.Comm.ICommDriver"/> object can
		/// be assigned to the <see cref="GsmDevice"/> instance during the
		/// initialization (object construction) of the instance; it can 
		/// also be assigned afterwards.
		/// <para>
		/// The value of the <b>CommDriver</b> property can be modified only 
		/// if the <see cref="GsmDevice"/> instance is not started (i.e. when
		/// the value of the <see cref="GsmDevice.IsStarted"/> property
		/// is false.
		/// </para>
		/// </remarks>
		/// <exception cref="ApplicationException">
		/// The <b>CommDriver</b> property is modified while the 
		/// <see cref="GsmDevice"/> instance is already started.
		/// </exception>
		public ICommDriver CommDriver
		{
			get { return this.commDriver; }
			set 
			{ 
				if (!this.isStarted)
				{
					if (this.commDriver != null)
						this.commDriver.Stop();
					
					this.commDriver = value; 									
				}
				else
				{
					string errorMsg = "Cannot change the CommDriver property because " +
						"the GsmDevice instance is already started.";

					throw new ApplicationException(errorMsg);
				}
			}
		}

		/// <summary>
		/// Gets or sets the <b>persitence provider</b> used by the
		/// <see cref="GsmDevice"/> instance. 
		/// </summary>
		/// <value>
		/// The <see cref="GsmLink.Persistence.IPersistenceProvider"/> object 
		/// that is currently used by the <see cref="GsmDevice"/> instance
		/// to save <see cref="SmsMessage"/> objects into a persistent data 
		/// store.
		/// </value>
		/// <remarks>
		/// Note: As of GSM-Link v0.1, this property not yet implemented, 
		/// and always returns null.
		/// </remarks>
		public IPersistenceProvider PersistenceProvider
		{
			get { return this.persistenceProvider; }
			set 
			{ 
				if (!this.isStarted)
				{					
					this.persistenceProvider = value; 
				}
				else
				{
					string errorMsg = "Cannot change the PersistenceProvider property because " +
						"the GsmDevice instance is already started.";

					throw new ApplicationException(errorMsg);
				}			
			}
		}

		/// <summary>
		/// Returns whether the <see cref="GsmDevice"/> instance is started.
		/// </summary>
		/// <value>
		/// <b>true</b> if the <see cref="GsmDevice"/> instance is started;
		/// <b>false</b> otherwise.
		/// </value>
		/// <remarks>
		/// The <b>IsStarted</b> property is automatically set when client
		/// code invokes the <see cref="GsmDevice.Start"/> or the 
		/// <see cref="GsmDevice.Stop"/> methods.
		/// </remarks>
		public bool IsStarted
		{
			get { return this.isStarted; }
		}

		/// <summary>
		/// Returns whether the <see cref="GsmDevice"/> instance' connection 
		/// to the physical GSM device is active.
		/// </summary>
		/// <value>
		/// <b>true</b> if the <see cref="GsmDevice"/> instance' connection  
		/// to the physical GSM device is active; <b>false</b> otherwise.
		/// </value>
		public bool IsConnected
		{
			get { return this.isConnected; }
		}
	
		/// <summary>
		/// Returns whether the GSM device supports PDU communication mode.
		/// </summary>
		/// <value>
		/// <b>true</b> if the GSM device supports PDU communication mode;
		/// <b>false</b> otherwise.
		/// </value>
		/// <remarks>
		/// If the valur of the <b>SupportsPduMode</b> property is <b>false</b>
		/// then the GSM device is incompatible with the GSM-Link library, and
		/// cannot be used.
		/// </remarks>
		public bool SupportsPduMode
		{
			get { return this.supportsPduMode; }
		}

		/// <summary>
		/// Gets or sets the storage area in the physical GSM device
		/// where SMS messages are saved, retrieved, and written.
		/// </summary>
		/// <value>
		/// The storage area in the physical GSM device where
		/// SMS messages are read and deleted from. The default value 
		/// of this propery is <b>MessageStorage.Device</b>, i.e. 
		/// it is assumed that the GSM device has its own memory. 
		/// Upon initialization, the <see cref="GsmDevice"/> instance
		/// will attempt to use the <b>built-in memory</b> of the GSM 
		/// device; if an error occurrs, the instance will instead use 
		/// the GSM device's <b>SIM memory</b>.
		/// </value>
		/// <remarks>
		/// The <b>MessageStorage</b> property can only be modified if 
		/// the value of the <see cref="GsmDevice.IsConnected"/> and 
		/// <see cref="GsmDevice.SupportsPduMode"/> are both <b>true</b>.
		/// <para>
		/// If the GSM device has built-in memory and the setting used
		/// is <b>MessageStorage.Sim</b> then the instance will not be
		/// able to read new messages from the GSM device; it will only
		/// be able to do so when the built-in memory is full, and the
		/// GSM device starts using its SIM memory.
		/// </para>
		/// <para>
		/// Some GSM devices can use both the built-in memory and SIM memory; 
		/// if this is so then <b>MessageStorage.Both</b> can be used as
		/// the value of the <b>MessageStorage</b> property.
		/// </para>
		/// </remarks>
		/// <exception cref="ApplicationException">
		/// The value assigned to the <b>MessageStorage</b> property
		/// is not a valid <see cref="GsmLink.MessageStorage"/> value. 
		/// </exception>
		public MessageStorage MessageStorage
		{
			get { return this.messageStorage; }
			set 
			{
				if (value == MessageStorage.Device || value == MessageStorage.Sim || value == MessageStorage.Both)
				{
					if (this.isConnected && this.supportsPduMode)
					{
						this.messageStorage = value;
						bool isSuccessful = SetMessageStorage(this.messageStorage);
						
						// If the SMS message storage was not changed then
						// the GSM device probably doesn't have its own memory,
						// and stores all messages on the SIM card.
						if (!isSuccessful && (this.messageStorage == MessageStorage.Device || 
							this.messageStorage == MessageStorage.Both) && this.isConnected) 
						{
							this.messageStorage = MessageStorage.Sim; 
							SetMessageStorage(this.messageStorage);
						}
					}
				}
				else
				{
					string errorMsg = value + " is not a valid value for the MessageStorage property.";
					throw new ApplicationException(errorMsg);
				}
			}
		}

		/// <summary>
		/// Gets or sets the number of times that the <see cref="GsmDevice"/> instance
		/// will automatically re-send an SMS message, after the first attempt at sending 
		/// the SMS message fails.
		/// </summary>
		/// <value>
		/// The number of times that the <see cref="GsmDevice"/> instance will automatically 
		/// re-send an SMS message, after the first attempt at sending the SMS message fails.
		/// </value>
		public int SendMessageRetryCount
		{
			get { return this.sendMessageRetryCount; }
			set
			{
				if (value >= 0)
					this.sendMessageRetryCount = value;
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="SyncOptions"/> that control how the 
		/// <see cref="GsmDevice"/> synchronizes its state with the physical 
		/// GSM device.
		/// </summary>
		/// <value>
		/// The current <see cref="SyncOptions"/> used by the <see cref="GsmDevice"/>
		/// instance to synchronizes its state with the physical GSM device.
		/// </value>
		public SyncOptions SyncOptions
		{
			get { return this.syncOptions; }
			set 
			{ 
				if (!this.isStarted)
				{
					this.syncOptions = value; 				
				}
				else
				{
					string errorMsg = "Cannot change the value of the SyncOptions property " +
						"if the GsmDevice instance is already started.";
					
					throw new ApplicationException(errorMsg);
				}
			}
		}

		/// <summary>
		/// Gets the <see cref="SmsMessageCollection"/> collection where all 
		/// SMS messages received by the <see cref="GsmDevice"/> instance are stored.
		/// </summary>
		/// <value>
		/// The <see cref="SmsMessageCollection"/> collection where all 
		/// SMS messages - represented by <see cref="SmsMessage"/> objects - 
		/// received by the <see cref="GsmDevice"/> instance are stored.
		/// </value>
		/// <remarks>
		/// When iterating throught the contents of the <b>Inbox</b> collection,
		/// it is recommeded that client code uses use the <b>lock</b> statement 
		/// and use the <b>Inbox</b> collection's <see cref="SmsMessageCollection.SyncRoot"/> 
		/// property as the lock object. 
		/// </remarks>
		public SmsMessageCollection Inbox
		{
			get { return this.inbox; }
		}

		/// <summary>
		/// Gets the <see cref="SmsMessageCollection"/> collection where outbound 
		/// SMS messages are stored by the <see cref="GsmDevice"/> instance.
		/// </summary>
		/// <value>
		/// The <see cref="SmsMessageCollection"/> collection where outbound  
		/// SMS messages - represented by <see cref="SmsMessage"/> objects - 
		/// are stored by the <see cref="GsmDevice"/> instance.
		/// </value>
		/// <remarks>
		/// When iterating throught the contents of the <b>Outbox</b> collection,
		/// it is recommeded that client code uses use the <b>lock</b> statement 
		/// and use the <b>Outbox</b> collection's <see cref="SmsMessageCollection.SyncRoot"/> 
		/// property as the lock object. 
		/// </remarks>
		public SmsMessageCollection Outbox
		{
			get { return this.outbox; }
		}

		/// <summary>
		/// Gets the <see cref="SmsMessageCollection"/> collection where all 
		/// successfully sent SMS messages are stored by the <see cref="GsmDevice"/> 
		/// instance.
		/// </summary>
		/// <value>
		/// The <see cref="SmsMessageCollection"/> collection where all 
		/// successfully sent SMS messages - represented by <see cref="SmsMessage"/> 
		/// objects - are stored by the <see cref="GsmDevice"/> instance.
		/// </value>
		/// <remarks>
		/// When iterating throught the contents of the <b>Sent</b> collection,
		/// it is recommeded that client code uses use the <b>lock</b> statement 
		/// and use the <b>Sent</b> collection's <see cref="SmsMessageCollection.SyncRoot"/> 
		/// property as the lock object. 
		/// </remarks>
		public SmsMessageCollection Sent
		{
			get { return this.sent; }
		}
		
		
		#endregion Properties

		#region Methods

		/// <summary>
		///	Starts the operation of the <see cref="GsmDevice"/> instance. 		
		/// </summary>
		/// <remarks>		
		/// The <b>Start</b> method performs the following tasks, in order:
		/// <list type="bullet">
		/// <item>
		/// Establishes a connection to the physical GSM device. (Note: A 
		/// valid <b>communication driver</b> (i.e. an object that supports
		/// the <see cref="GsmLink.Comm.ICommDriver"/> interface) must 
		/// already be assigned to the <see cref="GsmDevice"/> instance 
		/// before calling the <b>Start</b> method, otherwise an exception
		/// will be thrown.)
		/// </item>
		/// <item>
		/// Checks if the GSM device supports PDU mode. (Note: The device must
		/// support PDU mode, otherwise it is incompatible with the GSM-Link 
		/// library.)
		/// </item>
		/// <item>
		/// Attempts to change the <b>preferred message storage</b> setting of the 
		/// GSM device so that it matches the value of the <see cref="MessageStorage"/> 
		/// property. (Note: The GSM device must at least support SIM memory storage; 
		/// if the GSM device does not have built-in memory storage then the 
		/// <b>preferred message storage</b> setting is reverted to SIM memory 
		/// storage.)
		/// </item>
		/// <item>
		/// Starts background tasks that handle the asynchronous sending and 
		/// retrieval of SMS messages, and the raising of events to client  
		/// code. (Note: Background tasks will be started only if the <b>AutoSendReceive</b> 
		/// and/or the <b>AutoNotify</b> flags of the <see cref="GsmDevice"/> instance' 
		/// <see cref="GsmDevice.SyncOptions"/> property are set; these flags must be 
		/// modified before calling the <b>Start</b> method.
		/// </item>
		/// </list> 
		/// <para>
		/// Calling the <b>Start</b> method when the <see cref="GsmDevice"/> instance 
		/// is already started (i.e. the value of the <see cref="GsmDevice.IsStarted"/>
		/// property is <b>true</b>) has no effect.
		/// </para>		
		/// </remarks>
		/// <exception cref="ApplicationException">
		/// The <see cref="GsmDevice.CommDriver"/> property of the <see cref="GsmDevice"/> 
		/// instance is not set.
		/// </exception>				
		public void Start()
		{			
			if (this.isStarted)
				return;

			if (this.commDriver != null)
			{			
				if (!this.commDriver.IsStarted)				
					this.commDriver.Start();	
								
				this.isStarted = true;
				CheckDeviceStatus();
				StartAsyncTasks();
			}
			else
			{
				string errorMsg = "Cannot invoke the Start method on the GsmDevice instance " +
					"because the CommDriver property is not set.";

				throw new ApplicationException(errorMsg);
			}			
		}

		/// <summary>
		/// Checks the status of the connection to the physical GSM device.
		/// </summary>
		/// <remarks>
		/// The <b>CheckConnection</b> method checks the status of the connection 
		/// by "pinging" the physical GSM device. If the GSM device sends back 
		/// the expected reply then the connection is working properly; otherwise 
		/// the GSM device may be disconnected, or the GSM device may not support 
		/// AT commands (i.e. not compliant with <b>GSM 07.05</b>).
		/// <para>
		/// Note: The <see cref="ATCommands"/> class encapsulates the AT commands 
		/// that the <see cref="GsmDevice"/> class sends to the physical GSM device.
		/// </para>
		/// </remarks>
		/// <returns>
		/// <b>true</b> if the physical GSM device is currently connected;
		/// <b>false</b> otherwise.
		/// </returns>
		private bool CheckConnection()
		{
			bool isConnected = false;

			if (this.isStarted && this.commDriver != null && this.commDriver.IsStarted)
			{				
				string response = this.commDriver.Transact(ATCommands.PING, ATCommands.END_TOKENS,
					TimeSpan.FromSeconds(2));
			
				isConnected = Regex.IsMatch(response, ATCommands.SUCCESS);
				if (!isConnected && response != "")
				{
					Thread.Sleep(TimeSpan.FromSeconds(1));
					response = this.commDriver.Transact(ATCommands.PING, ATCommands.END_TOKENS,
						TimeSpan.FromSeconds(2));
			
					isConnected = Regex.IsMatch(response, ATCommands.SUCCESS);
				}
			}

			return isConnected;
		}		

		/// <summary>
		/// Checks if the GSM device supports PDU mode.
		/// </summary>
		/// <remarks>
		/// The <b>CheckPduModeSupport</b> method checks if the GSM device supports PDU mode 
		/// by attempting to enable PDU mode. The method does this by sending appropriate 
		/// AT commands to the GSM device. If the GSM device sends back the expected reply 
		/// then PDU mode is supported; if not then the GSM device cannot be "remote controlled" 
		/// through the <see cref="GsmDevice"/> class - it is incompatible with the GSM-Link
		/// library.
		/// <para>
		/// Note: The <b>CheckPduModeSupport</b> method sets to verbose mode the responses 
		/// sent by the physical GSM device before enabling PDU mode. It also disables command
		/// echo by the GSM device.
		/// </para>
		/// <para>
		/// The <see cref="ATCommands"/> class encapsulates the AT commands that the 
		/// <see cref="GsmDevice"/> class sends to the physical GSM device.
		/// </para>
		/// </remarks>
		/// <returns>
		/// <b>true</b> if PDU mode is supported and currently enabled; <b>false</b> 
		/// otherwise.
		/// </returns>
		private bool CheckPduModeSupport()
		{
			bool isSuccessful = false;

			if (this.isConnected) 
			{
				bool supportsVerboseMode = false;
				bool supportsPduMode = false;

				// Set the response to verbose mode.
				string response = this.commDriver.Transact(ATCommands.V1, ATCommands.END_TOKENS,
					TimeSpan.FromSeconds(2));

				if (Regex.IsMatch(response, ATCommands.SUCCESS))
					supportsVerboseMode = true;

				// Disable command echo. This is not essential for proper operation,
				// so it does not affect the success of the method.
				this.commDriver.Transact(ATCommands.E0, ATCommands.END_TOKENS, TimeSpan.FromSeconds(2));

				// Set the message format to PDU.
				response = this.commDriver.Transact(ATCommands.CMGF_0, ATCommands.END_TOKENS,
					TimeSpan.FromSeconds(2));
			
				if (Regex.IsMatch(response, ATCommands.SUCCESS))
					supportsPduMode = true;

				if (supportsVerboseMode && supportsPduMode)
					isSuccessful = true;				
			}			

			return isSuccessful;
		}

		/// <summary>
		/// Sends an AT command to the physical GSM device to change the preferred 
		/// message storage.
		/// </summary>
		/// <param name="storage">
		/// The new <see cref="MessageStorage"/> value.
		/// </param>
		/// <returns>
		/// <b>true</b> is successful; <b>false</b> otherwise.
		/// </returns>
		private bool SetMessageStorage(MessageStorage storage)
		{
			bool isSuccessful = false;

			if (this.isStarted && this.commDriver != null && this.commDriver.IsStarted)
			{
				string response = this.commDriver.Transact(ATCommands.CPMS(storage), 
					ATCommands.END_TOKENS, TimeSpan.FromSeconds(5));
					
				if (Regex.IsMatch(response, ATCommands.SUCCESS))
					isSuccessful = true;				
			}	

			return isSuccessful;
		}
		
		/// <summary>
		/// Checks the status of physical GSM device.
		/// </summary>
		/// <remarks>
		/// If the <b>CheckDeviceStatus</b> method detects that the physical 
		/// GSM device is newly connected or is re-connected, it resets the 
		/// configuration of the device to use PDU mode and to use the message 
		/// storage specified by the current value of the <see cref="MessageStorage"/> 
		/// property.		 
		/// <para>
		/// Invoking this method may raise either the <see cref="Connected"/> 
		/// or the <see cref="Disconnected"/> event depending on whether 
		/// the method detects that the physical device has transitioned from 
		/// the disconnected state to the connected state, and vice versa; 
		/// it also depends on whether or not the <b>AutoNotify</b> flag of the 
		/// <see cref="SyncOptions"/> property is set.
		/// </para>
		/// </remarks> 		
		public void CheckDeviceStatus()
		{
			bool isConnected2 = CheckConnection();	
			bool isBatteryLevelChecked = false;
			bool isSignalQualityChecked = false;
			
			// Check if the GSM device has transitioned from the connected state
			// to the disconnected state, or vice versa.
			if (this.isConnected != isConnected2)
			{	
				this.isConnected = isConnected2;
				if (this.isConnected)
				{					
					// Set the message format of the GSM device to PDU mode. 					
					this.supportsPduMode = CheckPduModeSupport();					
					
					// Set the preferred message storage to the current value 
					// of the MessageStorage property.					
					MessageStorage = this.messageStorage;					
					
					// Get the GSM device's manufacturer, model, serial number.					
					GetDeviceInfo();	
				
					// Check the GSM device's battery level
					// and the signal quality.
					if (this.isConnected && this.supportsPduMode)
					{						
						CheckBatteryLevel(false);
						isBatteryLevelChecked = true;

						CheckSignalQuality(false);
						isSignalQualityChecked = true;
					}	
					
					// Raise the Connected event - because the GSM device is
					// newly connected, or re-connected.
					if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
					{
						if (this.asyncTasksStarted)
							OnConnected(EventArgs.Empty);
						else
							RaiseEvent(this.Connected, EventArgs.Empty);			
					}
				}
				else
				{
					// Raise the Disconnected event.
					if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
					{
						if (this.asyncTasksStarted)
							OnDisconnected(EventArgs.Empty);
						else
							RaiseEvent(this.Disconnected, EventArgs.Empty);			
					}					
				}
			}

			// Check the GSM device's battery level.
			if (!isBatteryLevelChecked && this.isConnected && this.supportsPduMode)
			{				
				CheckBatteryLevel(true);
			}
			
			// Check the signal quality.
			if (!isSignalQualityChecked && this.isConnected && this.supportsPduMode)
			{				
				CheckSignalQuality(true);
			}		
		}		
	
		private void GetDeviceInfo()
		{
			if (this.isConnected && this.supportsPduMode)
			{
				string lastManufacturer = this.manufacturer;
				string lastModel = this.model;
				string lastSerialNumber = this.serialNumber;

				// Get the manufacturer ID
				string response = this.commDriver.Transact(ATCommands.CGMI, ATCommands.END_TOKENS,
					TimeSpan.FromSeconds(2));
			
				if (Regex.IsMatch(response, ATCommands.SUCCESS))
				{					
					this.manufacturer = Regex.Replace(response, @"[\s+|(OK)]", "");
				}

				// Get the model ID
				response = this.commDriver.Transact(ATCommands.CGMM, ATCommands.END_TOKENS,
					TimeSpan.FromSeconds(2));
			
				if (Regex.IsMatch(response, ATCommands.SUCCESS))
				{					
					this.model = Regex.Replace(response, @"[\s+|(OK)]", "");
				}

				// Get the serial number
				response = this.commDriver.Transact(ATCommands.CGSN, ATCommands.END_TOKENS,
					TimeSpan.FromSeconds(2));
			
				if (Regex.IsMatch(response, ATCommands.SUCCESS))
				{					
					this.serialNumber = Regex.Replace(response, @"[\s+|(OK)]", "");
				}				

				// Check if the GSM device has been swapped with a different device
				// i.e. the previous device was disconnected and then replaced
				// with another device.

				if ((lastManufacturer != "" && lastManufacturer != this.manufacturer) || 
					(lastModel != "" && lastModel != this.model) ||
					(lastSerialNumber != "" && lastSerialNumber != this.serialNumber))
				{
					//Raise the Swapped event.					
					if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
					{
						SwappedEventArgs e = new SwappedEventArgs(lastManufacturer, lastModel, lastSerialNumber);
						if (this.asyncTasksStarted)
						{
							OnSwapped(e);
						}
						else
						{
							RaiseEvent(this.Swapped, e);			
						}
					}
				}				
			}
		}

		private void CheckBatteryLevel(bool raiseEvent)
		{
			if (this.isConnected && this.supportsPduMode)
			{
				// Check the GSM device's power level.
				string response = this.commDriver.Transact(ATCommands.CBC, ATCommands.END_TOKENS, 
					TimeSpan.FromSeconds(2));

				if (Regex.IsMatch(response, ATCommands.SUCCESS))
				{					
					int oldBatteryLevel = this.batteryLevel;					

					string source = Regex.Replace(Regex.Match(response, @"\s\d,").Value, @"[\s|(OK)|,]", "");					
					if (source == "0")
					{
						// The GSM device is using a battery as the power source.
						this.isUsingBattery = true;
						string level = Regex.Match(response, @",\d+").Value.Replace(",", "");
						this.batteryLevel = Convert.ToInt32(level);						
					}
					else
					{
						// The GSM device is not using a battery.
						// Assume that the power level is 100 percent.
						this.isUsingBattery = false;
						this.batteryLevel = 100;						
					}
					
					if (raiseEvent && this.batteryLevel != oldBatteryLevel && oldBatteryLevel != -1)
					{
						// Raise the BatteryLevelChanged event.
						if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
						{
							BatteryLevelChangedEventArgs e = new BatteryLevelChangedEventArgs(this.batteryLevel);
							if (this.asyncTasksStarted)
							{
								OnBatteryLevelChanged(e);
							}
							else
							{
								RaiseEvent(BatteryLevelChanged, e);			
							}
						}
					}
				}
			}			
		}

		private void CheckSignalQuality(bool raiseEvent)
		{
			if (this.isConnected && this.supportsPduMode)
			{
				// Check the GSM device's power level.
				string response = this.commDriver.Transact(ATCommands.CSQ, ATCommands.END_TOKENS, 
					TimeSpan.FromSeconds(2));

				if (Regex.IsMatch(response, ATCommands.SUCCESS))
				{					
					int oldSignalQuality = this.signalQuality;

					string temp = Regex.Replace(Regex.Match(response, @"\s\d+,").Value, @"[\s|(OK)|,]", "");					
					int newSignalQuality = Convert.ToInt32(temp);
					
					// NOTE: The AT+CSQ command returns values 
					// between 0 - 31 as the signal quality. 
					// If the value returned is 99 -> unknown, 
					// or no signal detected.
					if (newSignalQuality == 99)
					{
						// The signal quality is unknown.
						this.signalQuality = 0;
					}
					else
					{
						// Normalize the signal quality to 100%
						// so that a value of 0 is mapped to 3%
						// and a value of 31 is mapped to 100%.						
						this.signalQuality = (int)((((decimal)newSignalQuality + 1) / 32) * 100); 
					}									
					
					if (raiseEvent && this.signalQuality != oldSignalQuality && oldSignalQuality != -1)
					{
						// Raise the SignalQualityChanged event.
						if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
						{
							SignalQualityChangedEventArgs e = new SignalQualityChangedEventArgs(this.signalQuality);
							if (this.asyncTasksStarted)
							{
								OnSignalQualityChanged(e);
							}
							else
							{
								RaiseEvent(SignalQualityChanged, e);			
							}
						}
					}
				}
			}			

		}

		/// <summary>
		/// Starts threads that continously poll the physical
		/// GSM device for new messages, send queued messages, and notify 
		/// clients about relevant device events.
		/// </summary>
		private void StartAsyncTasks()
		{
			// Start the notification thread.
			if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
			{
				this.notifyThread = new Thread(new ThreadStart(Notify));
				this.notifyThread.Name = "Notify";
				this.notifyThread.IsBackground = true;							
				this.notifyQueue.Clear();
				this.notifySignal = new ManualResetEvent(false);
				this.notifyThread.Start();			
			}

			// Start the send-receive thread.
			if ((this.syncOptions & SyncOptions.AutoSendReceive) == SyncOptions.AutoSendReceive)
			{
				this.sendReceiveThread = new Thread(new ThreadStart(SendReceive));
				this.sendReceiveThread.Name = "SendReceive";
				this.sendReceiveThread.IsBackground = true;
				this.sendReceiveSignal = new ManualResetEvent(false);
				this.sendReceiveThread.Start();
			}

			this.asyncTasksStarted = true;
		}

		/// <summary>
		/// Stops the threads that continously poll the physical
		/// GSM device for new messages, send queued messages, and notify 
		/// clients about relevant device events.
		/// </summary>
		private void StopAsyncTasks()
		{			
			if (this.notifyThread != null)				
			{
				this.notifyThread.Abort();				
				this.notifyThread = null;
			}					
				
			if (this.sendReceiveThread != null)
			{
				this.sendReceiveThread.Abort();
				this.sendReceiveThread = null;
			}

			if (this.notifySignal != null)
				this.notifySignal.Close();

			if (this.sendReceiveSignal != null)
				this.sendReceiveSignal.Close();
			
			this.asyncTasksStarted = false;
		}

		/// <summary>
		/// Stops the operation of the <see cref="GsmDevice"/> instance.
		/// </summary>
		/// <remarks>
		/// Invoking the <b>Stop</b> method on the <see cref="GsmDevice"/> 
		/// instance disconnects it from the physical GSM device, stops 
		/// asynchronous tasks, and releases the resources used by the 
		/// instance.
		/// <para>
		/// Calling the <b>Stop</b> method automatically raises the 
		/// <see cref="Disconnected"/> event.
		/// </para>
		/// </remarks>
		public void Stop()
		{
			if (this.isStarted)
			{	
				if (this.commDriver != null && this.commDriver.IsStarted)
				{	
					// Enable command echo. Applicable only if command echo was disabled earlier.
					this.commDriver.Transact(ATCommands.E1, ATCommands.END_TOKENS, TimeSpan.FromSeconds(2));

					// Stop the communication driver.
					this.commDriver.Stop();	
				}
			
				StopAsyncTasks();			

				// Manually raise the Disconnected event, because the notification 
				// thread was already aborted.
				if (this.isConnected && (this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
					RaiseEvent(this.Disconnected, EventArgs.Empty);								

				this.isConnected = false;
				this.supportsPduMode = false;
				this.isStarted = false;
			}						
		}
		
		/// <summary>
		/// Sends an SMS message.
		/// </summary>
		/// <param name="message">
		/// A <see cref="SmsMessage"/> object that contains information about 
		/// the SMS message to send.		
		/// </param>
		/// <remarks>		
		/// The behavior of the <b>SendMessage</b> method depends on the value of 
		/// the <b>AutoSendReceive</b> flag of the <see cref="GsmDevice.SyncOptions"/> 
		/// property. 
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is set, all messages sent using the 
		/// <b>SendMessage</b> method are enqueued in the <see cref="GsmDevice"/>'s 
		/// <see cref="GsmDevice.Outbox"/> collection; the actual sending is performed 
		/// asynchronously. The return value of the <b>SendMessage</b> method is always 
		/// <b>true</b>, unless an error occurred during the enqueueing process. 
		/// </para>
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is not set, then all messages sent 
		/// through the <b>SendMessage</b> method are sent directly to the physical 
		/// GSM device. The return value of the <b>SendMessage</b> method is the 
		/// actual result of the sending process.
		/// </para>
		/// </remarks>
		/// <exception cref="System.ApplicationException">
		/// Either of the following:
		/// <list type="bullet">
		/// <item>The <see cref="GsmDevice"/> is not yet started.</item>
		/// <item>The <b>message</b> parameter is null.</item>
		/// <item>The <see cref="SmsMessage.Type"/> property of the <b>message</b> 
		/// parameter is not <see cref="MessageType.SmsSubmit"/>.</item>
		/// </list>
		/// </exception>		
		/// <returns>
		/// <b>true</b> if the SMS message was successfully sent;
		/// <b>false</b> otherwise.
		/// </returns>
		public bool SendMessage(SmsMessage message)
		{
			bool queued = ((this.syncOptions & SyncOptions.AutoSendReceive) ==
				SyncOptions.AutoSendReceive) ? true : false;

            this.messageToSend++;
			return SendMessage(message, queued);
		}

		/// <summary>
		/// Sends an SMS message with the option of either enqueueing it, 
		/// or immediately sending it.
		/// </summary>
		/// <param name="message">
		/// A <see cref="SmsMessage"/> object that contains information about 
		/// the SMS message to send.	
		/// </param>
		/// <param name="queued">
		/// If <b>true</b> then the SMS message will be enqueued in the 
		/// <see cref="GsmDevice"/>'s <see cref="Outbox"/> collection; 
		/// if <b>false</b> then the SMS message will be immediately sent 
		/// to the GSM device.
		/// </param>		
		/// <exception cref="System.ApplicationException">
		/// Either of the following:
		/// <list type="bullet">
		/// <item>The <see cref="GsmDevice"/> is not yet started.</item>
		/// <item>The <b>message</b> parameter is null.</item>
		/// <item>The <see cref="SmsMessage.Type"/> property of the <b>message</b> 
		/// parameter is not <see cref="MessageType.SmsSubmit"/>.</item>
		/// </list>
		/// </exception>		
		/// <returns>
		/// <b>true</b> if the SMS message was successfully sent;
		/// <b>false</b> otherwise.
		/// </returns>
		public bool SendMessage(SmsMessage message, bool queued)
		{
			bool isSuccessful = false;
			
			if (this.isStarted)
			{	
				if (message != null && message.Type == MessageType.SmsSubmit)
				{
					// Check the value of the queued parameter. If it is true
					// then put the message in the Outbox queue and return true. 
					// If not then send the message directly to the GSM device.		
					
					if (queued)
					{
						// Add the SmsMessage object to the end of the Outbox
						// collection.
						this.outbox.Add(message);
						if (this.sendReceiveSignal != null)
							this.sendReceiveSignal.Set();						

						isSuccessful = true;
					}
					else
					{						
						string pduString = message.GetPduString();
						int pduLength = (pduString.Length / 2) - 1;

						string[] commands = new string[2];
						commands[0] = ATCommands.CMGS(pduLength);
						commands[1] = pduString + ATCommands.CTRL_Z;

						string[][] endTokens = new string[2][];
						endTokens[0] = ATCommands.END_TOKENS;
						endTokens[1] = ATCommands.END_TOKENS;

						string[] successTokens = new string[2];
						successTokens[0] = ATCommands.PROMPT;
						successTokens[1] = ATCommands.SUCCESS;

						string[] responses;
						
						int retryCount = 0;						
						do
						{
							// Send the message to the physical GSM device.	
							string finalResponse = this.commDriver.Transact(commands, endTokens, successTokens,
								out responses, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(20));

							if (Regex.IsMatch(finalResponse, ATCommands.SUCCESS))
								isSuccessful = true;							
						
							// NOTE: If the first attempt to send the SMS message fails,
							// then retry the process - the number of times to retry is 
							// the value of the SendMessageRetryCount property.
							if (!isSuccessful)
							{								
								if (finalResponse == "")								
									break;								
								
								CheckDeviceStatus();
					
								if (retryCount == this.sendMessageRetryCount)
									break;
								else if (retryCount < this.sendMessageRetryCount)
									retryCount += 1;
								
							}

						}while (!isSuccessful && this.isConnected && this.supportsPduMode);

						if (isSuccessful)
						{						
							message.SetMessageStatus(MessageStatus.Sent);
							this.sent.Insert(0, message);
							if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
								OnSendMessageSucceeded(new SendMessageResultEventArgs(retryCount, message));							
						}							
						else
						{
							// If the message was not successfully sent, but the number of 
							// retry attempts is less than the value of the SendMessageRetryCount 
							// property (the GSM device was probably disconnected while in the
							// middle of the process) then re-insert the message at the end 
							// of the outbox collection.
							if (retryCount < this.SendMessageRetryCount)
							{								
								this.outbox.Add(message);
							}
							else
							{
								if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
									OnSendMessageFailed(new SendMessageResultEventArgs(retryCount, message));								
							}
						}
					}
				}
				else
				{
					string errorMsg = "Cannot send SMS message because ";
					if (message == null)
					{
						errorMsg = errorMsg + "the SmsMessage passed the SendMessage method is null.";
					}
					else if (message.Type != MessageType.SmsSubmit)
					{
						errorMsg = errorMsg + "the Type property of the SmsMessage instance passed to the " +
							"SendMessage method is not MessageType.SmsSubmit.";
					}
					throw new ApplicationException(errorMsg);
				}
			}
			else
			{
				string errorMsg = "Cannot invoke the SendMessage method on the GsmDevice instance " +
					"because the instance is not started.";

				throw new ApplicationException(errorMsg);
			}			

			return isSuccessful;
		}
		
		/// <summary>
		/// Sends a text message to the specified destination number.
		/// </summary>
		/// <param name="phoneNumber">
		/// The destination telephone number, in international format
		/// (e.g. +639171234567, or +63 (917) 123-4567, or 639171234567)
		/// </param>
		/// <param name="text">
		/// The text message (maximum of 160 characters) to send.
		/// </param>
		/// <remarks>		
		/// The behavior of the <b>SendMessage</b> method depends on the value of 
		/// the <b>AutoSendReceive</b> flag of the <see cref="GsmDevice.SyncOptions"/> 
		/// property. 
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is set, all messages sent using the 
		/// <b>SendMessage</b> method are enqueued in the <see cref="GsmDevice"/>'s 
		/// <see cref="GsmDevice.Outbox"/> collection; the actual sending is performed 
		/// asynchronously. The return value of the <b>SendMessage</b> method is always 
		/// <b>true</b>, unless an error occurred during the enqueueing process. 
		/// </para>
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is not set, then all messages sent 
		/// through the <b>SendMessage</b> method are sent directly to the physical 
		/// GSM device. The return value of the <b>SendMessage</b> method is the 
		/// actual result of the sending process.
		/// </para>
		/// </remarks>
		/// <exception cref="System.ApplicationException">
		/// Either of the following:
		/// <list type="bullet">
		/// <item>The <see cref="GsmDevice"/> is not yet started.</item>
		/// <item>The <b>message</b> parameter is null.</item>
		/// <item>The <see cref="SmsMessage.Type"/> property of the <b>message</b> 
		/// parameter is not <see cref="MessageType.SmsSubmit"/>.</item>
		/// </list>
		/// </exception>	
		/// <returns>
		/// <b>true</b> if the text message was successfully sent;
		/// <b>false</b> otherwise.
		/// </returns>
		public bool SendMessage(string phoneNumber, string text)
		{
			bool queued = ((this.syncOptions & SyncOptions.AutoSendReceive) ==
				SyncOptions.AutoSendReceive) ? true : false;

			return SendMessage(phoneNumber, text, queued);
		}
				
		/// <summary>
		/// Sends a text message to the specified destination number, 
		/// with the option of either enqueueing it, or immediately 
		/// sending it.
		/// </summary>
		/// <param name="phoneNumber">
		/// The destination telephone number, in international format
		/// (e.g. +639171234567, or +63 (917) 123-4567, or 639171234567)
		/// </param>
		/// <param name="text">
		/// The text message (maximum of 160 characters) to send.
		/// </param>
		/// <param name="queued">
		/// If <b>true</b> then the SMS message will be enqueued in the 
		/// <see cref="GsmDevice"/>'s <see cref="Outbox"/> collection; 
		/// if <b>false</b> then the SMS message will be immediately sent 
		/// to the GSM device.
		/// </param>
		/// <exception cref="System.ApplicationException">
		/// Either of the following:
		/// <list type="bullet">
		/// <item>The <see cref="GsmDevice"/> is not yet started.</item>
		/// <item>The <b>message</b> parameter is null.</item>
		/// <item>The <see cref="SmsMessage.Type"/> property of the <b>message</b> 
		/// parameter is not <see cref="MessageType.SmsSubmit"/>.</item>
		/// </list>
		/// </exception>	
		/// <returns>
		/// <b>true</b> if the text message was successfully sent;
		/// <b>false</b> otherwise.
		/// </returns>		
		public bool SendMessage(string phoneNumber, string text, bool queued)
		{	
			SmsMessage message = new SmsMessage(phoneNumber, text);
			return SendMessage(message, queued);
		}
		
		/// <summary>
		/// Sends a data message to the specified destination number.		
		/// </summary>
		/// <param name="phoneNumber">
		/// The destination telephone number, in international format
		/// (e.g. +639171234567, or +63 (917) 123-4567, or 639171234567)
		/// </param>
		/// <param name="data">
		/// A <see cref="System.Byte"/> array containing the 8-bit data
		/// (maximum of 140 bytes) to send as an SMS message.
		/// </param>
		/// <remarks>		
		/// The behavior of the <b>SendMessage</b> method depends on the value of 
		/// the <b>AutoSendReceive</b> flag of the <see cref="GsmDevice.SyncOptions"/> 
		/// property. 
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is set, all messages sent using the 
		/// <b>SendMessage</b> method are enqueued in the <see cref="GsmDevice"/>'s 
		/// <see cref="GsmDevice.Outbox"/> collection; the actual sending is performed 
		/// asynchronously. The return value of the <b>SendMessage</b> method is always 
		/// <b>true</b>, unless an error occurred during the enqueueing process. 
		/// </para>
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is not set, then all messages sent 
		/// through the <b>SendMessage</b> method are sent directly to the physical 
		/// GSM device. The return value of the <b>SendMessage</b> method is the 
		/// actual result of the sending process.
		/// </para>
		/// </remarks>
		/// <exception cref="System.ApplicationException">
		/// Either of the following:
		/// <list type="bullet">
		/// <item>The <see cref="GsmDevice"/> is not yet started.</item>
		/// <item>The <b>message</b> parameter is null.</item>
		/// <item>The <see cref="SmsMessage.Type"/> property of the <b>message</b> 
		/// parameter is not <see cref="MessageType.SmsSubmit"/>.</item>
		/// </list>
		/// </exception>	
		/// <returns>
		/// <b>true</b> if the data message was successfully sent;
		/// <b>false</b> otherwise.
		/// </returns>
		public bool SendMessage(string phoneNumber, byte[] data)
		{
			bool queued = ((this.syncOptions & SyncOptions.AutoSendReceive) ==
				SyncOptions.AutoSendReceive) ? true : false;

			return SendMessage(phoneNumber, data, queued);
		}

		/// <summary>
		/// Sends a data message to the specified destination number,		
		/// with the option of either enqueueing it, or immediately 
		/// sending it.
		/// </summary>
		/// <param name="phoneNumber">
		/// The destination telephone number, in international format
		/// (e.g. +639171234567, or +63 (917) 123-4567, or 639171234567)
		/// </param>
		/// <param name="data">
		/// A <see cref="System.Byte"/> array containing the 8-bit data
		/// (maximum of 140 bytes) to send as an SMS message.
		/// </param>
		/// <param name="queued">
		/// If <b>true</b> then the SMS message will be enqueued in the 
		/// <see cref="GsmDevice"/>'s <see cref="Outbox"/> collection; 
		/// if <b>false</b> then the SMS message will be immediately sent 
		/// to the GSM device.
		/// </param>
		/// <exception cref="System.ApplicationException">
		/// Either of the following:
		/// <list type="bullet">
		/// <item>The <see cref="GsmDevice"/> is not yet started.</item>
		/// <item>The <b>message</b> parameter is null.</item>
		/// <item>The <see cref="SmsMessage.Type"/> property of the <b>message</b> 
		/// parameter is not <see cref="MessageType.SmsSubmit"/>.</item>
		/// </list>
		/// </exception>	
		/// <returns>
		/// <b>true</b> if the data message was successfully sent;
		/// <b>false</b> otherwise.
		/// </returns>
		public bool SendMessage(string phoneNumber, byte[] data, bool queued)
		{
			SmsMessage message = new SmsMessage(phoneNumber, data);
			return SendMessage(message, queued);
		}
		
		/// <summary>
		/// Searches for and returns all <see cref="SmsMessage"/> objects whose 
		/// <see cref="SmsMessage.Status"/> matches the specified <see cref="MessageStatus"/> 
		/// value.
		/// </summary>
		/// <param name="status">
		/// The <see cref="MessageStatus"/> of the <see cref="SmsMessage"/> objects
		/// to search for.
		/// </param>
		/// <returns>
		/// An array of <see cref="SmsMessage"/> objects if successful; an empty
		/// array (of type <see cref="SmsMessage"/>) otherwise.
		/// </returns>
		/// <remarks>		
		/// The behavior of the <b>GetMessages</b> method depends on the value of 
		/// the <b>AutoSendReceive</b> flag of the <see cref="GsmDevice.SyncOptions"/> 
		/// property. 
		/// <para>
		/// If the <b>AutoSendReceive</b> flag is set, all messages will be 
		/// retrieved fromt he <see cref="GsmDevice"/> instance's message caches 
		/// - the <see cref="GsmDevice.Inbox"/>, the <see cref="GsmDevice.Outbox"/>, 
		/// and <see cref="GsmDevice.Sent"/>; if the <b>AutoSendReceive</b> flag 
		/// is not set, then all messages are retrieved direct from the physical 
		/// GSM device.
		/// </para>
		/// </remarks>
		public SmsMessage[] GetMessages(MessageStatus status)
		{
			bool fromCache = ((this.syncOptions & SyncOptions.AutoSendReceive) == 
				SyncOptions.AutoSendReceive) ? true : false;
			
			return GetMessages(status, fromCache);
		}

		/// <summary>
		/// Searches for and returns all <see cref="SmsMessage"/> objects whose 
		/// <see cref="SmsMessage.Status"/> matches the specified <see cref="MessageStatus"/> 
		/// value, with the option to search either from the <see cref="GsmDevice"/>'s 
		/// message caches or directly from the physical GSM device.
		/// </summary>
		/// <param name="status">
		/// The <see cref="MessageStatus"/> of the <see cref="SmsMessage"/> objects
		/// to search for.
		/// </param>
		/// <param name="fromCache">
		/// If <b>true</b> then the <b>GetMessages</b> method performs the search on the 
		/// <see cref="GsmDevice"/> instance's message caches - the <see cref="GsmDevice.Inbox"/>, 
		/// <see cref="GsmDevice.Outbox"/>, and <see cref="GsmDevice.Sent"/> collections; 
		/// if <b>false</b> then the <b>GetMessages</b> method performs the search on the 
		/// physical GSM device's message storage.
		/// </param>
		/// <returns>
		/// An array of <see cref="SmsMessage"/> objects if successful; an empty
		/// <see cref="SmsMessage"/> array otherwise.
		/// </returns>
		public SmsMessage[] GetMessages(MessageStatus status, bool fromCache)
		{
			SmsMessage[] messages = new SmsMessage[0];
			
			if (fromCache)
			{		
				// Get the messages from the inbox, outbox, sent collections.
				if (this.isStarted)
				{	
					ArrayList buffer = new ArrayList();
					
					if (status == MessageStatus.Unread || status == MessageStatus.Read || 
						status == MessageStatus.All)
					{
						lock(this.inbox.SyncRoot)
						{
							foreach (SmsMessage message in this.inbox)
							{
								if ((status == MessageStatus.Unread && message.Status == MessageStatus.Unread ) ||
									(status == MessageStatus.Read && message.Status == MessageStatus.Read) ||
									(status == MessageStatus.All))
								{
									buffer.Add(message);
								}								
							}
						}						
					}
					
					if (status == MessageStatus.Unsent || status == MessageStatus.All)
					{
						lock(this.outbox.SyncRoot)
						{						
							foreach (SmsMessage message in this.outbox)
								buffer.Add(message);							
						}						
					}

					if (status == MessageStatus.Sent || status == MessageStatus.All)
					{
						lock(this.sent.SyncRoot)
						{
							foreach (SmsMessage message in this.sent)
							{								
								buffer.Add(message);
							}
						}						
					}

					messages = new SmsMessage[buffer.Count];
					buffer.CopyTo(messages, 0);				
				}
			}
			else 
			{
				// Get the messages from the physical GSM device.
				if (this.isConnected && this.supportsPduMode)
				{
					string response = this.commDriver.Transact(ATCommands.CMGL(status), ATCommands.END_TOKENS, 
						TimeSpan.FromSeconds(30));
				
					// Process the response string even if is incomplete,
					// i.e. no need to check for ATCommands.SUCCESS, 
					// as long as the response string is not blank.				

					if (response != "")
					{				
						string pduPattern = ATCommands.CRLF + @"[\d|A-F|a-f]+" + ATCommands.CRLF;
						string headerPattern = @"\d+,\d+,.*,\d+";
						string fullPattern = headerPattern + pduPattern;
					
						MatchCollection matches = Regex.Matches(response, fullPattern);
						if (matches.Count > 0)
						{
							messages = new SmsMessage[matches.Count];
							for (int i = 0; i < matches.Count; i++)
							{		
								Match headerMatch = Regex.Match(matches[i].Value, headerPattern);
								string headerString = headerMatch.Value;

								Match pduMatch = Regex.Match(matches[i].Value, pduPattern);
								string pduString = pduMatch.Value.Replace(ATCommands.CRLF, "");
								
								Match indexMatch = Regex.Match(headerString, @"^\d+");
								int index = Convert.ToInt32(indexMatch.Value);

								Match statusMatch = Regex.Match(headerString, @",\d+");
								int stat = Convert.ToInt32(statusMatch.Value.Replace(",", ""));

								// Create a SmsMessage object to encapsulate the message.
								messages[i] = new SmsMessage(pduString);
								messages[i].Index = index;													
								messages[i].SetMessageStatus((MessageStatus)stat);
							}
						}
					}				
				}
				else
				{
					string errorMsg = "Cannot invoke the GetMessages method on the GsmDevice instance because ";
					if (!this.isConnected)
						errorMsg = errorMsg + "the GSM device is not connected";
				
					if (this.isConnected && !this.supportsPduMode)
						errorMsg = errorMsg + "the GSM device does not support PDU mode.";
				
					throw new ApplicationException(errorMsg);				
				}
			}
			
			return messages;
		}

		/// <summary>
		/// Retrieves an SMS message from the physical GSM device.
		/// </summary>
		/// <param name="index">
		/// The index of the SMS message to retrieve.
		/// </param>
		/// <returns>
		/// A <see cref="SmsMessage"/> object if successful; 
		/// <b>null</b> otherwise.
		/// </returns>
		public SmsMessage GetMessage(int index)
		{
			SmsMessage message = null;

			if (this.isConnected && this.supportsPduMode)
			{
				string response = this.commDriver.Transact(ATCommands.CMGR(index), ATCommands.END_TOKENS, 
					TimeSpan.FromSeconds(30));
				
				// Process the response string even if it's incomplete,
				// i.e. no need to check for ATCommands.SUCCESS, as
				// long as the response string is not blank.		

				if (response != "")
				{	
					string headerPattern = @"\d+,.*,\d+";
					string pduPattern = ATCommands.CRLF + @"[\d|A-F|a-f]+" + ATCommands.CRLF;					
					string fullPattern = headerPattern + pduPattern;
					
					Match fullMatch = Regex.Match(response, fullPattern);					
					if (fullMatch != null)
					{						
						Match headerMatch = Regex.Match(fullMatch.Value, headerPattern);
						Match pduMatch = Regex.Match(fullMatch.Value, pduPattern);						
						
                        string headerString = headerMatch.Value;
						string pduString = pduMatch.Value.Replace(ATCommands.CRLF, "");						
						int status = Convert.ToInt32(Regex.Match(headerString, @"^\d+").Value);						
						
						message = new SmsMessage(pduString);
						message.Index = index;							
						message.SetMessageStatus((MessageStatus)status);						
					}
				}				
			}
			else
			{
				string errorMsg = "Cannot invoke the GetMessage method on the GsmDevice instance because ";
				if (!this.isConnected)
					errorMsg = errorMsg + "the GSM device is not connected";
				
				if (this.isConnected && !this.supportsPduMode)
					errorMsg = errorMsg + "the GSM device does not support PDU mode.";
				
				throw new ApplicationException(errorMsg);
			}

			return message;
		}				

		/// <summary>
		/// Deletes an SMS message from the physical GSM device.
		/// </summary>
		/// <param name="index">
		/// The index of the SMS to delete from the physical GSM device.
		/// </param>
		/// <param name="verify">
		/// If <b>true</b>, the <b>DeleteMessage</b> method will first
		/// verify if the SMS message exists before actually deleting it 
		/// from the GSM device.
		/// </param>
		/// <returns>
		/// <b>true</b> if the SMS message was successfully deleted;
		/// <b>false</b> otherwise.
		/// </returns>
		public bool DeleteMessage(int index, bool verify)
		{
			bool isSuccessful = false;

			if (this.isConnected && this.supportsPduMode)
			{				
				if (verify)
				{
					SmsMessage message = GetMessage(index);
					if (message != null)
                        isSuccessful = true;
				}

				if (!verify || (verify && isSuccessful))
				{
					string response = this.CommDriver.Transact(ATCommands.CMGD(index), ATCommands.END_TOKENS, 
						TimeSpan.FromSeconds(5));

					isSuccessful = Regex.IsMatch(response, ATCommands.SUCCESS);
				}				
			}
			else
			{
				string errorMsg = "Cannot invoke the DeleteMessage method on the GsmDevice instance because ";
				if (!this.isConnected)
					errorMsg = errorMsg + "the GSM device is not connected";
				
				if (this.isConnected && !this.supportsPduMode)
					errorMsg = errorMsg + "the GSM device does not support PDU mode.";
				
				throw new ApplicationException(errorMsg);
			}

			return isSuccessful;
		}
		
		/// <summary>
		/// Performs the asynchronous sending and receiving 
		/// of SMS messages.
		/// </summary>
		private void SendReceive()
		{
			int messagesToSend = 1;
			int messagesReceived = 0;

			while(true)
			{
				try
				{					
					// Send any unsent messages in the Outbox.					
					// The number of messages to send depends on 
					// the number of new messages received.
					messagesToSend = (messagesReceived > 1) ? messagesReceived : 1;
					SendUnsentMessages(messagesToSend);	

					// Check for new messages in the GSM device's inbox.					
					messagesReceived = GetNewMessages();

					// TODO: Call the PersistenceProvider's SaveMessages method
					// to save the contents of the GsmDevice's inbox, outbox,
					// and sent collections to persistent storage.

					// If there are no more messages to send, or if the GSM device
					// was disconnected or does not support PDU mode, then block 
					// this thread until any of the following occurs:
					//  --> sendReceiveSignal is set (by the SendMessage() method)
					//  --> a time span of 1 second expires
					if (this.outbox.Count == 0 || !this.isConnected || !this.supportsPduMode)
					{
						this.sendReceiveSignal.Reset();
						this.sendReceiveSignal.WaitOne(TimeSpan.FromSeconds(1), true);
					}	
				} 
				catch {}
			}			
		}

		/// <summary>
		/// Sends SMS messages that were enqueued in the <see cref="GsmDevice"/>
		/// instance's <see cref="GsmDevice.Outbox"/>.
		/// </summary>
		/// <param name="messageCount">
		/// The number of <see cref="SmsMessage"/> objects to send.
		/// </param>
		private void SendUnsentMessages(int messageCount)
		{			
			int messagesSent = 0;

			// Check the status of the GSM device first
			// before sending SMS messages.
			CheckDeviceStatus();														
			
			while (this.outbox.Count > 0 && messagesSent < messageCount &&
				this.isConnected && this.supportsPduMode)
			{					
				SmsMessage message = this.outbox.GetMessage(0);
				if (message != null)
				{
					SendMessage(message, false);
					messagesSent += 1;
				}										
			}													
		}		

		/// <summary>
		/// Retrieves all unread SMS messages from the physical GSM device.
		/// </summary>
		/// <returns>
		/// The number of SMS messages retrieved from the physical GSM device.
		/// </returns>
		private int GetNewMessages()
		{
			int messageCount = 0;
			SmsMessage[] messages = null;

			// Check the status of the GSM device first
			// before retrieving SMS messages.
			CheckDeviceStatus();

			if (this.isConnected && this.supportsPduMode)
			{
				// Retrieve new messages from the physical GSM device.
				messages = GetMessages(MessageStatus.Unread, false);			
				messageCount = messages.GetLength(0);			
			}

			if (messageCount > 0)
			{
				// Add the new messages to the Inbox collection.					
				for (int i = 0; i < messageCount; i++)
				{
					SmsMessage message = messages[i];
					if (message != null)
					{
						// Insert the message at the beginning of the collection,
						// so that inbox[0] always contains the most recently 
						// received message.
						this.inbox.Insert(0, message);						
					}
				}
				
				// Raise the MessageReceived event.
				if ((this.syncOptions & SyncOptions.AutoNotify) == SyncOptions.AutoNotify)
					OnMessageReceived(new MessageReceivedEventArgs(messages));				

				// If the AutoDelete flag is set, then delete the new 
				// SMS messages from the physical GSM device's inbox.
				if ((this.syncOptions & SyncOptions.AutoDelete) == SyncOptions.AutoDelete)
				{
					for (int i = 0; i < messageCount; i++)
					{
						SmsMessage message = messages[i];
						if (message != null && this.isConnected && this.supportsPduMode)
						{							
							bool isSuccessful = DeleteMessage(message.Index, false); 
							if (!isSuccessful)
							{								
								CheckDeviceStatus();
								if (!this.isConnected || !this.supportsPduMode)
									break;
							}							
						}						
					}
				}				
			}			

			return messageCount;
		}

		/// <summary>
		/// Enqueues an event (represented by a delegate instance) for 
		/// later processing by the notification thread.
		/// </summary>
		/// <param name="d">
		/// A <see cref="System.Delegate"/> instance that will be invoked
		/// asynchronously by the notification thread.
		/// </param>
		/// <param name="e">
		/// An <see cref="EventArgs"/> instance that contains data for 
		/// the event.
		/// </param>		
		private void EnqueueEvent(Delegate d, EventArgs e)
		{
			this.notifyQueue.Enqueue(new object[]{d, e});										
			if (this.notifySignal != null)
				this.notifySignal.Set();
		}

		/// <summary>
		/// Processes events that were enqueued by the <see cref="GsmDevice"/> 
		/// instance.
		/// </summary>
		private void Notify()
		{				
			while(true)
			{
				try
				{
					if (this.notifyQueue.Count > 0)
					{
						object[] eventData = (object[])this.notifyQueue.Dequeue();						
						RaiseEvent((Delegate)eventData[0], (EventArgs)eventData[1]);
					}				

					if (this.notifyQueue.Count == 0)
					{
						// If there is no event to be raised then block 
						// the thread until notifySignal is set.
						this.notifySignal.Reset();
						this.notifySignal.WaitOne();
					}
				}			
				catch {}					
			}			
		}
		
		/// <summary>
		/// Performs the actual raising of events to client code.
		/// </summary>
		/// <param name="d">
		/// A <see cref="System.Delegate"/> instance that represents
		/// the event to be raised.
		/// </param>
		/// <param name="e">
		/// An <see cref="EventArgs"/> instance that contains data for 
		/// the event.
		/// </param>		
		private void RaiseEvent(Delegate d, EventArgs e)
		{
			if (d != null)
			{
				Delegate[] invocationList = d.GetInvocationList();
				foreach (Delegate del in invocationList)
				{
					try
					{
						// If the object that handles the event 
						// is a Windows Forms control, then 
						// the event must be marshalled to the 
						// UI thread.
						Control ctrl = del.Target as Control;
						if  (ctrl != null && ctrl.InvokeRequired)
						{
							ctrl.Invoke(del, new object[]{this, e});
						}
						else
						{					
							del.DynamicInvoke(new object[]{this, e});												
						}
					}
					catch {}
				}
			}
		}		

		
		#endregion Methods

		#region Events, event methods	

		/// <summary>
		/// Occurs when the <see cref="GsmDevice"/> instance detects that
		/// the physical GSM device transitions from the <b>disconnected</b> 
		/// to the <b>connected</b> state.
		/// </summary>
		/// <remarks>
		/// The <b>Connected</b> event is raised only if the 
		/// <b>AutoNotify</b> flag of the <see cref="SyncOptions"/> property
		/// is set.		
		/// <para>
		/// Normally, the <b>Connected</b> event is raised from a thread which 
		/// is separate from the thread in which the client code is executing -  
		/// hence the method that handles the event must be thread safe. 
		/// However if the event handling method is part of a Windows Forms 
		/// application, the <b>Connected</b> event is automatically marshalled 
		/// to the same thread (the UI thread) where the Windows Forms application 
		/// is executing.
		/// </para> 
		/// <para>
		/// When client code invokes the <see cref="GsmDevice.Start"/> method -
		/// which raises the <b>Connected</b> event - the event is raised in 
		/// the same thread as the client code.
		/// </para>
		/// </remarks>
		public event EventHandler Connected;

		/// <summary>
		/// Occurs when the <see cref="GsmDevice"/> instance detects that
		/// the physical GSM device transistions from the <b>connected</b> 
		/// to the <b>disconnected</b> state.
		/// </summary>
		/// <remarks>
		/// The <b>Disconnected</b> event is raised only if the 
		/// <b>AutoNotify</b> flag of the <see cref="SyncOptions"/> property
		/// is set.		
		/// <para>
		/// Normally, the <b>Disconnected</b> event is raised from a thread which 
		/// is separate from the thread in which the client code is executing -  
		/// hence the method that handles the event must be thread safe. 
		/// However if the event handling method is part of a Windows Forms 
		/// application, the <b>Disconnected</b> event is automatically marshalled 
		/// to the same thread (the UI thread) where the Windows Forms application 
		/// is executing.
		/// </para> 
		/// <para>
		/// When client code invokes the <see cref="GsmDevice.Stop"/> method -
		/// which raises the <b>Disconnected</b> event - the event is raised in 
		/// the same thread as the client code.		
		/// </para>
		/// </remarks>
		public event EventHandler Disconnected;	
	
		/// <summary>
		/// Occurs when the <see cref="GsmDevice"/> instance detects that
		/// the physical GSM device is swapped with another GSM device.
		/// </summary>
		/// <remarks>
		/// The <see cref="GsmDevice"/> instance detects the occurrence of a 
		/// device swap by comparing the current values of the <see cref="Manufacturer"/>,
		/// <see cref="Model"/>, and <see cref="SerialNumber"/> properties with the
		/// values that are retrieved from the periodic polling of the physical GSM
		/// device.
		/// <para>
		/// The <b>Swapped</b> event is raised only if the <b>AutoNotify</b> flag 
		/// of the <see cref="SyncOptions"/> property is set.				
		/// </para>
		/// </remarks>
		public event SwappedEventHandler Swapped;

		/// <summary>
		/// Occurs when the <see cref="GsmDevice"/> instance detects that
		/// the charge level of the GSM device's battery has changed.
		/// </summary>		
		public event BatteryLevelChangedEventHandler BatteryLevelChanged;

		/// <summary>
		/// Occurs when the <see cref="GsmDevice"/> instance detects that
		/// the quality of the signal received by the GSM device has changed.
		/// </summary>		
		public event SignalQualityChangedEventHandler SignalQualityChanged;

		/// <summary>
		/// Occurrs when the <see cref="GsmDevice"/> instance receives 
		/// new SMS messages from the physical GSM device.
		/// </summary>
		public event MessageReceivedEventHandler MessageReceived;

		/// <summary>
		/// Occurrs when the <see cref="GsmDevice"/> instance successfully
		/// sends an SMS message (represented by an instance of the 
		/// <see cref="SmsMessage"/> class).
		/// </summary>
		public event SendMessageResultEventHandler SendMessageSucceeded;

		/// <summary>
		/// Occurrs when the <see cref="GsmDevice"/> instance fails   
		/// to send an SMS message (represented by an instance of the 
		/// <see cref="SmsMessage"/> class).
		/// </summary>
		public event SendMessageResultEventHandler SendMessageFailed;

		/// <summary>
		/// Raises the <see cref="Connected"/> event.
		/// </summary>
		/// <param name="e">
		/// An <see cref="EventArgs"/> object that contains the event data. 
		/// </param>
		protected virtual void OnConnected(EventArgs e)
		{
			if (this.Connected != null)
			{				
				EnqueueEvent(this.Connected, e);
			}
		}

		/// <summary>
		/// Raises the <see cref="Disconnected"/> event.
		/// </summary>
		/// <param name="e">
		/// An <see cref="EventArgs"/> object that contains the event data. 
		/// </param>
		protected virtual void OnDisconnected(EventArgs e)
		{
			if (this.Disconnected != null)
			{
				EnqueueEvent(this.Disconnected, e);
			}
		}

		/// <summary>
		/// Raises the <see cref="Swapped"/> event.
		/// </summary>
		/// <param name="e">
		/// A <see cref="SwappedEventArgs"/> object that contains the event data. 		
		/// </param>
		protected virtual void OnSwapped(SwappedEventArgs e)
		{
			if (this.Swapped != null)
			{
				EnqueueEvent(this.Swapped, e);
			}

		}

		/// <summary>
		/// Raises the <see cref="BatteryLevelChanged"/> event.
		/// </summary>
		/// <param name="e">
		/// A <see cref="BatteryLevelChangedEventArgs"/> object that contains 
		/// the event data. 		
		/// </param>
		protected virtual void OnBatteryLevelChanged(BatteryLevelChangedEventArgs e)
		{
			if (this.BatteryLevelChanged != null)
			{
				EnqueueEvent(this.BatteryLevelChanged, e);
			}

		}

		/// <summary>
		/// Raises the <see cref="SignalQualityChanged"/> event.
		/// </summary>
		/// <param name="e">
		/// A <see cref="SignalQualityChangedEventArgs"/> object that contains 
		/// the event data. 		
		/// </param>
		protected virtual void OnSignalQualityChanged(SignalQualityChangedEventArgs e)
		{
			if (this.SignalQualityChanged != null)
			{
				EnqueueEvent(this.SignalQualityChanged, e);
			}
		}

		/// <summary>
		/// Raises the <see cref="MessageReceived"/> event.
		/// </summary>
		/// <param name="e">
		/// A <see cref="MessageReceivedEventArgs"/> object that contains 
		/// the event data. 
		/// </param>
		protected virtual void OnMessageReceived(MessageReceivedEventArgs e)
		{
			if (this.MessageReceived != null)
			{
				EnqueueEvent(this.MessageReceived, e);
			}
		}

		/// <summary>
		/// Raises the <see cref="SendMessageSucceeded"/> event.
		/// </summary>
		/// <param name="e">
		/// A <see cref="SendMessageResultEventArgs"/> object that contains 
		/// the event data. 
		/// </param>
		protected virtual void OnSendMessageSucceeded(SendMessageResultEventArgs e)
		{
			if (this.SendMessageSucceeded != null)
			{
				EnqueueEvent(this.SendMessageSucceeded, e);
			}
		}

		/// <summary>
		/// Raises the <see cref="SendMessageFailed"/> event.
		/// </summary>
		/// <param name="e">
		/// A <see cref="SendMessageResultEventArgs"/> object that contains 
		/// the event data. 
		/// </param>
		protected virtual void OnSendMessageFailed(SendMessageResultEventArgs e)
		{
			if (this.SendMessageFailed != null)
			{
				EnqueueEvent(this.SendMessageFailed, e);
			}
		}

		#endregion Events, event methods		
	}

	/// <summary>
	/// Encapsulates AT commands used by the <see cref="GsmDevice"/> class
	/// to communicate with a GSM device. These AT commands are defined in the 
	/// <b>GSM 07.05</b> and <b>GSM 07.07</b>technical specifications.
	/// </summary>	
	internal sealed class ATCommands
	{
		#region Constructor (private)
			
		private ATCommands()
		{
		}

		#endregion 
	
		#region Constants, read-only fields

		/// <summary>
		/// Represents the carriage return character.
		/// </summary>
		public const char CR = (char)0x0D;

		/// <summary>
		/// Represents the linefeed character.
		/// </summary>
		public const char LF = (char)0x0A;

		/// <summary>
		/// Represents the carriage-return-linefeed combination.
		/// </summary>
		public static readonly string CRLF = String.Concat(CR, LF); 
		
		//		/// <summary>
		//		/// Represents the escape character.
		//		/// </summary>
		//		public const char ESC = (char)0x1B;

		
		/// <summary>
		/// Represents the control-Z character.
		/// </summary>
		public const char CTRL_Z = (char)0x1A;
		
		/// <summary>
		/// Represents a string token that, when present at  
		/// the end of the response string received from the
		/// GSM device, signifies that the most recently 
		/// issued AT command was successfully processed.
		/// </summary>
		public const string SUCCESS = "OK";

		/// <summary>
		/// Represents a string token that, when present at  
		/// the end of the response string received from the
		/// GSM device, signifies that there was an error
		/// processing the most recently issued AT command.
		/// </summary>
		public const string ERROR = "ERROR";

		/// <summary>
		/// Represents a string token that is received 
		/// from the GSM device afer issuing the 
		/// AT command for sending an SMS message.
		/// </summary>
		public const string PROMPT = ">";
		
		/// <summary>
		/// Represents the set of string tokens that mark the end
		/// of the response string received from the physical GSM device.
		/// </summary>
		public static readonly string[] END_TOKENS = new string[]{SUCCESS, ERROR, PROMPT};				
		
		
		/// <summary>
		/// Represents the AT command for setting the response
		/// of the GSM device to verbose mode.		
		/// </summary>
		/// <remarks>
		/// This AT command is not part of the <b>GSM</b> technical
		/// specifications.
		/// </remarks>
		public static readonly string V1 = "ATV1" + CR;

		/// <summary>
		/// Represents the AT command for disabling command echo
		/// by the GSM device.
		/// </summary>
		/// <remarks>
		/// This AT command is not part of the <b>GSM</b> technical
		/// specifications.
		/// </remarks>
		public static readonly string E0 = "ATE0" + CR;

		/// <summary>
		/// Represents the AT command for enabling command echo
		/// by the GSM device.
		/// </summary>
		/// <remarks>
		/// This AT command is not part of the <b>GSM</b> technical
		/// specifications.
		/// </remarks>
		public static readonly string E1 = "ATE1" + CR;
		
		/// <summary>
		/// Represents the AT command for testing the connectivity
		/// or reachability of the physical GSM device.
		/// </summary>
		public static readonly string PING = "AT" + CR;			
		
		/// <summary>
		/// Represents the AT command for setting the message format to PDU mode.
		/// </summary>
		public static readonly string CMGF_0 = "AT+CMGF=0" + CR;	
		
		/// <summary>
		/// Represents the AT command for retrieving the manufactucturer ID 
		/// of the physical GSM device.
		/// </summary>
		public static readonly string CGMI = "AT+CGMI" + CR;

		/// <summary>
		/// Represents the AT command for retrieving the model ID of the
		/// physical GSM device.
		/// </summary>
		public static readonly string CGMM = "AT+CGMM" + CR;

		/// <summary>
		/// Represents the AT command for retrieving the serial number of the
		/// physical GSM device.
		/// </summary>
		public static readonly string CGSN = "AT+CGSN" + CR;

		/// <summary>
		/// Represents the AT command for retrieving the power level of the
		/// physical GSM device.
		/// </summary>
		public static readonly string CBC = "AT+CBC" + CR;

		/// <summary>
		/// Represents the AT command for retrieving the signal quality of the
		/// physical GSM device.
		/// </summary>
		public static readonly string CSQ = "AT+CSQ" + CR;

		#endregion Constants, read-only fields
	
		#region Methods

		/// <summary>
		/// Represents an AT command template for setting the preferred 
		/// SMS message storage.
		/// </summary>
		/// <param name="storage">
		/// A <see cref="MessageStorage"/> value that specifies the preferred 
		/// SMS message storage.
		/// </param>
		/// <returns>
		/// The full AT command string for setting the preferred SMS message 
		/// to the <see cref="MessageStorage"/> value given by the <b>storage</b> 
		/// parameter.
		/// </returns>
		public static string CPMS(MessageStorage storage)
		{
			string pms = "MT";
			switch (storage)
			{
				case MessageStorage.Both:
					pms = "MT";
					break;

				case MessageStorage.Device:
					pms = "ME";
					break;
				
				case MessageStorage.Sim:
					pms = "SM";
					break;
			}			
			string cmd = String.Format("AT+CPMS=\"{0}\"" + CR, pms);
			return cmd;
		}
		
		/// <summary>
		/// Represents an AT command template for listing SMS messages.
		/// </summary>
		/// <param name="status">
		/// A <see cref="MessageStatus"/> value that specifies which  
		/// SMS messages will be listed. 
		/// </param>
		/// <returns>
		/// The full AT command string for listing all SMS messages whose 
		/// <see cref="MessageStatus"/> is given by the <b>status</b> 
		/// parameter. 
		/// </returns>
		public static string CMGL(MessageStatus status)
		{
			string cmd = String.Format("AT+CMGL={0}" + CR, (int)status);
			return cmd;			
		}

		/// <summary>
		/// Represents an AT command template for reading an SMS message. 
		/// </summary>
		/// <param name="index">
		/// The index of the SMS message to read. 
		/// </param>
		/// <returns>
		/// The full AT command string for reading the SMS message whose 
		/// index is given by the <b>index</b> parameter.
		/// </returns>
		public static string CMGR(int index)
		{
			return String.Format("AT+CMGR={0}" + CR, index);
		}	

		/// <summary>
		/// Represents an AT command template for deleting an SMS message.
		/// </summary>
		/// <param name="index">
		/// The index of the message to delete.
		/// </param>
		/// <returns>
		/// The full AT command string for deleting the SMS message whose 
		/// index is given by the <b>index</b> parameter.
		/// </returns>
		public static string CMGD(int index)
		{
			return String.Format("AT+CMGD={0}" + CR, index);
		}

		/// <summary>
		/// Represents an AT command template for sending an SMS message.
		/// </summary>
		/// <param name="length">
		/// The length of the PDU byte stream encapsulating the SMS message. 
		/// If the PDU byte stream has a leading zero (0x00) the zero byte 
		/// is not included in the calculation for the length.
		/// </param>
		/// <returns>
		/// The full AT command string for sending the SMS message whose 
		/// PDU length is given by the <b>length</b> parameter.
		///</returns>
		public static string CMGS(int length)
		{
			string cmd = String.Format("AT+CMGS={0}" + CR, length);
			return cmd;
		}

		#endregion Methods
	}
	
	


}
